package org.lous.lax;


import java.io.Reader;
import java.io.IOException;

import org.lous.lax.Atom;


public class LaxIn
{
  // PUBLIC STATIC STUFF:

	public LaxIn(Reader src){
    if (null == src) return;
    this.src = src;
    try{
      next = src.read();
    }
    catch (IOException iox){
      iox.printStackTrace();
    }
  }
  

	
  // PUBLIC INSTANCE STUFF:
	
  private Reader src;
  private int next = -1; // next character to process
  private StringBuilder valueStack = new StringBuilder(); // valueStack buffer
  private Atom.Type type = Atom.Type.UNDEFINED;
  
  public Atom getNext() throws IOException
  {
    // throw out the no content
    if (null == src) return null;
    clearValue();
    skipSpace();
    if (-1 == next) return null;
		
    // found something
    if ((int)'<' != next){
      return data();
    }
    
    // Not data. What then?
    next = src.read();
    if (-1 == next) return starttag(); //'<' at end of file. Report as broken start tag.
		
    if ( (int)'/' == next ){
      skipChar();
      return endtag();
    }
    else if ( (int)'?' == next ){
      skipChar();
      return meta();
    }
    else if ( (int)'!' == next ){
      // possibly a comment
      valuePushChar(); // but in case it's not
      if (-1 == next) return starttag();
      if ( (int)'-' == next ){
        // could still be
        valuePushChar(); // but in case it's not
        if (-1 == next) return starttag();
        if ( (int)'-' == next ){
          // it is definitely a comment.
          skipChar();
          clearValue();
          return comment();
        }
      }
    }

    // it was an opening tag all along
    return starttag();
  }

	// PRIVATE INSTANCE STUFF
  
  private Atom data() throws IOException
  {
    while ( -1 != next ){
      valuePushChar();
      if ( (int)'<' == next){ // finished.
        // don't skip: angle is part of next atom.
        return new Atom (Atom.Type.DATA, getValue());
      }
    }
    // unexpected end of file:
    return new Atom (Atom.Type.DATA, getValue(), false);
  }
  
  
  private Atom starttag() throws IOException
  {
    // normal case:
    while ( -1 != next ){
      if ( (int)'>' == next){
        skipChar();
        //TODO: handle closedtags
        return new Atom(Atom.Type.STARTTAG, getValue());
      }
      valuePushChar();
    }
    // unexpected end of file:
    return new Atom(Atom.Type.STARTTAG, getValue(), false);
  }
  
  
  private Atom endtag() throws IOException
  { 
    while ( -1 != next ){
      if ( (int)'>' == next){
        skipChar();
        return new Atom(Atom.Type.ENDTAG, getValue());
      }
      valuePushChar();
    }
    // unexpected end of file:
    return new Atom(Atom.Type.ENDTAG, getValue(), false);
  }

  
  private Atom comment() throws IOException
  { 
    while ( -1 != next ){
      if ( (int)'>' == next){ // possible end of comment
        // look behind:
        int len = valueStack.length();
        if (
          (valueStack.charAt(len-1) == '-')
          &&
          (valueStack.charAt(len-2) == '-')
        ){
          // end of comment reached
          skipChar();
          valueStack.setLength(len-2);
          return new Atom(Atom.Type.COMMENT, getValue());
        }
			  // naah, just a bracket inside the comment. Move along.
      }
      valuePushChar();
    }
    // unexpected end of file:
    return new Atom(Atom.Type.COMMENT, getValue(), false);
  }

  private Atom meta() throws IOException
  { 
    while ( -1 != next ){
      if ( (int)'>' == next){  // possible end of meta
        // look behind:
        int len = valueStack.length();
        if (valueStack.charAt(len-1) == '?'){ 
          //end of meta reached 
          skipChar();
          valueStack.setLength(len-1);
          return new Atom(Atom.Type.META, getValue());
        }
      }
      valuePushChar();
    }
    // unexpected end of file:
    return new Atom(Atom.Type.META, getValue(), false);
  }

  
  private void skipChar() throws IOException{
    next = src.read();
  }

  
  private void skipSpace() throws IOException{
    while (Character.isWhitespace(next)){
      skipChar();
    }
  }

  
  private void valuePushChar() throws IOException{
    if (-1 == next) return;
    valueStack.append( (char)next );
    next = src.read();
  }
  
  
  private String getValue(){
    return valueStack.toString().trim();
  }
  
  private void clearValue(){
    valueStack.setLength(0);
  }

	
	// SELF-TEST STUFF
	
  public static void test(){
    Reader src = new java.io.StringReader(
      "<? doctype crarp ?> <hello> abc <!--foo <-> --> ghb </hello>"
    );
    LaxIn stream = new LaxIn(src);
    try{
      Atom atom = stream.getNext();
      while (null != atom) {
        System.out.println(atom.type + " " + atom.value);
        atom = stream.getNext();
      } 
    } 
    catch (Exception e){
      e.printStackTrace();
    }
  }
  
}
